package controller;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import loader.SlidingBoxLoader;
import model.Block;
import model.SlidingBox;
import solver.PuzzleSolver;

import java.io.File;
import java.net.URL;
import java.util.Collection;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class IndexController implements Initializable {

    @FXML
    private VBox playbackBox;
    @FXML
    private Button openButton;
    @FXML
    private Button solveButton;
    @FXML
    private GridPane blockPane;
    @FXML
    private Label statusLabel;

    /**
     * 异步任务运行器
     */
    private ExecutorService executor;
    /**
     * 当前的滑块盒状态
     */
    private ObjectProperty<SlidingBox> currentState;

    /**
     * 构造方法
     */
    public IndexController() {
        this.currentState = new SimpleObjectProperty<>();
    }

    /**
     * 读取并加载初始关卡数据
     */
    @FXML
    public void open() {
        //初始化线程池
        this.executor = Executors.newSingleThreadExecutor();
        FileChooser fileChooser = new FileChooser();
        fileChooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter("选择关卡数据文件", ".txt"));
        //默认路径
        fileChooser.setInitialDirectory(new File(System.getProperty("user.dir") + "\\resources\\puzzles"));
        File file = fileChooser.showOpenDialog(null);
        if (file != null && file.exists()) {
            //构造异步加载任务并把状态绑定到控件上
            SlidingBoxLoader slidingBoxLoader = new SlidingBoxLoader(file);
            this.statusLabel.textProperty().bind(slidingBoxLoader.messageProperty());
            //加载完成修改当前滑块盒，并切换按钮状态
            slidingBoxLoader.setOnSucceeded(event -> {
                SlidingBox slidingBox = (SlidingBox) event.getSource().getValue();
                this.currentState.setValue(slidingBox);
                this.renderBox(slidingBox);
                this.openButton.setDisable(true);
                this.solveButton.setDisable(false);
            });
            //启动异步加载任务
            this.executor.submit(slidingBoxLoader);
            this.executor.shutdown();
        }
    }

    /**
     * 把滑块盒显示在界面上
     *
     * @param slidingBox 滑块盒
     */
    private void renderBox(SlidingBox slidingBox) {
        //清空
        this.blockPane.getChildren().clear();
        slidingBox.getBlocks().values().stream()
                .flatMap(Collection::stream)
                .forEach(block -> {
                    int[] range = block.getRange();
                    Label label = createLabels(block);
                    this.blockPane.add(label,
                            range[0],
                            range[1],
                            range[2] - range[0] + 1,
                            range[3] - range[1] + 1);
                });
    }

    /**
     * 根据滑块创建标签控件
     *
     * @param block
     *
     * @return
     */
    private Label createLabels(Block block) {
        int[] range = block.getRange();
        Label label = new Label();
        label.setAlignment(Pos.CENTER);
        label.setText(String.valueOf(block.getCode()));
        label.getStyleClass().add("block");
        label.setPrefWidth((range[2] - range[0] + 1) * 100);
        label.setPrefHeight((range[3] - range[1] + 1) * 100);
        //特殊滑块
        if (block.getCode() == '1') {
            label.getStyleClass().add("block-primary");
        } else if (block.getCode() == '0') {
            label.getStyleClass().add("block-empty");
        }
        return label;
    }

    /**
     * 求解滑块步骤
     */
    @FXML
    private void solve() {
        //启动求解器并注册响应事件
        //求解按钮能点击时，说明滑块盒已经加载完成
        PuzzleSolver puzzleSolver = new PuzzleSolver(this.currentState.get());
        //监听异步任务值的变化
        this.statusLabel.textProperty().unbind();
        this.statusLabel.textProperty().bind(puzzleSolver.messageProperty());
        this.currentState.unbind();
        this.currentState.bind(puzzleSolver.valueProperty());
        this.currentState.addListener((observable, oldValue, newValue) -> {
            this.renderBox(newValue);
        });
        puzzleSolver.setOnSucceeded(event -> {
            Object object = event.getSource().getValue();
            if (object != null) {
                SlidingBox slidingBox = (SlidingBox) object;
                this.renderPlayback(slidingBox);
            }
        });
        this.executor = Executors.newSingleThreadExecutor();
        //启动求解的异步任务
        this.executor.submit(puzzleSolver);
        this.executor.shutdown();
    }

    /**
     * 回看每个移动步骤
     *
     * @param resultBox
     */
    private void renderPlayback(SlidingBox resultBox) {
        this.playbackBox.getChildren().clear();
        ToggleGroup toggleGroup = new ToggleGroup();

        SlidingBox currentBox = resultBox;
        while (currentBox != null) {
            RadioButton currentRadio = createRadio(currentBox, toggleGroup);
            currentRadio.setSelected(currentBox == resultBox);
            this.playbackBox.getChildren().add(currentRadio);
            currentBox = currentBox.getPrevSlidingBox();
        }
        //选项变化时，重绘滑块盒
        toggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
            this.renderBox((SlidingBox) newValue.getUserData());
        });
    }

    /**
     * 创建步骤单选按钮
     *
     * @param box
     * @param toggleGroup
     *
     * @return
     */
    private RadioButton createRadio(SlidingBox box, ToggleGroup toggleGroup) {
        RadioButton radioButton = new RadioButton();
        radioButton.setUserData(box);//将状态绑定到控件上以便随时访问
        radioButton.setToggleGroup(toggleGroup);
        radioButton.setText(String.valueOf(box.getSteps()));
        return radioButton;
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
    }
}
